/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.language.lexis;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;

import org.modelcc.ModelCCProcess;
import org.modelcc.io.java.Reflection;
import org.modelcc.language.factory.SymbolIdentifier;
import org.modelcc.language.metamodel.LanguageElement;
import org.modelcc.language.metamodel.LanguageModel;
import org.modelcc.language.metamodel.SimpleLanguageElement;
import org.modelcc.lexer.Token;

import test.modelcc.language.types.model.ObjectCaster;

/**
 * Token builder.
 *
 * @author Luis Quesada (lquesada@modelcc.org) & Fernando Berzal (fberzal@modelcc.org)
 */
public class TokenBuilder extends ModelCCProcess implements Serializable 
{	
	private LanguageModel model;
	
	/**
	 * Constructor
	 * @param model Language model
	 */
	public TokenBuilder (LanguageModel model)
	{
		this.model = model;
	}
	
	/**
	 * Get language model
	 * @return The language model
	 */
	public LanguageModel getModel ()
	{
		return model;
	}
	
	
    /**
     * Builds a token, filling its data, and validates it.
     * @param t token to be built.
     * @return true if the token is valid, false if not
     */
	public boolean build (Token t) 
	{
        SymbolIdentifier eid = (SymbolIdentifier)t.getType();
        SimpleLanguageElement be = (SimpleLanguageElement) eid.getElement();
        Class c = be.getElementClass();
        boolean valid = true;
        Object o;
        try {
            o = c.newInstance();

            if (be.getValueField() != null) {
            	Field fld = Reflection.findField(c,be.getValueField());
                if (fld != null) {
                    fld.setAccessible(true);
                    fld.set(o, ObjectCaster.cast(fld.getType(), t.getValue()));
                }
            }
            runSetupMethods(o,be);
            valid &= runConstraints(o,be);
            t.setUserData(o);

        } catch (Exception ex) {
            log(Level.SEVERE, "Token instantiation exception", ex);
            return false;
        }

        return valid;
    }

	protected final void runSetupMethods(Object o,LanguageElement el) 
			throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException 
	{
		if (model.getSuperelements().get(el) != null) {
			runSetupMethods(o,model.getSuperelements().get(el));
		}
        if (el.getSetupMethod() != null) {
            Method mtd = el.getElementClass().getDeclaredMethod(el.getSetupMethod(),new Class[]{});
            if (mtd != null) {
                mtd.setAccessible(true);
                try {
                    mtd.invoke(o);
				} catch (Exception e) {
	                log(Level.SEVERE, "Exception when invoking method \"{0}\" of class class \"{1}\".", new Object[]{mtd.getName(),el.getElementClass().getCanonicalName()});
					throw e;
				}
            }
        }
	}

	private boolean runConstraints(Object o, LanguageElement el) 
			throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException 
	{
		boolean valid = true;
		if (model.getSuperelements().get(el) != null) {
			valid &= runConstraints(o,model.getSuperelements().get(el));
		}
        for (int i = 0;i < el.getConstraintMethods().size();i++) {
            Method mtd = el.getElementClass().getDeclaredMethod(el.getConstraintMethods().get(i),new Class[]{});
            if (mtd != null) {
                mtd.setAccessible(true);
                try {
                    valid &= (Boolean)mtd.invoke(o);
				} catch (Exception e) {
	                log(Level.SEVERE, "Exception when invoking method \"{0}\" of class class \"{1}\".", new Object[]{mtd.getName(),el.getElementClass().getCanonicalName()});
					throw e;
				}
            }
        }
		return valid;
	}
}
